package org.custom.spring.boot.security.token.impl;

import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.ObjectUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;
import org.custom.spring.boot.security.entity.SystemUser;
import org.custom.spring.boot.security.entity.SystemUserInfo;
import org.custom.spring.boot.security.entity.security.SecurityProperties;
import org.custom.spring.boot.security.exceptions.JwtException;
import org.custom.spring.boot.security.token.TokenManager;
import org.custom.spring.boot.security.utils.SecurityUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Optional;

import static io.jsonwebtoken.SignatureAlgorithm.HS256;
import static org.cleaver.basic.constant.CleaverConstant.EMPTY;
import static org.custom.spring.boot.security.constant.SecurityConstant.*;

/**
 * Redis Token 令牌管理器
 *
 * @author LiKun
 */
@Service
@Validated
public class RedisTokenManager implements TokenManager {
    private static final String PATH = "/approve/login";

    @Value("${token.secret}")
    private String secret;

    @Resource
    private Snowflake snowflake;
    @Resource
    private ObjectMapper objectMapper;
    @Resource
    private SecurityProperties securityProperties;
    @Resource
    private transient RedisTemplate<String, Object> redisTemplate;

    /**
     * 向用户数据中填充相关数据
     *
     * @param systemUser 用户数据
     *
     * @return 用户数据
     */
    private static SystemUser updateData4SystemUser(SystemUser systemUser, String path) {
        // 获取当前数据库时间
        Date current = SecurityUtil.getCurrentTime();

        // 填充相关数据信息
        systemUser.setPassword(EMPTY);
        systemUser.setLastAccess(current);
        systemUser.setLastAccessPath(path);
        if (ObjectUtil.isNull(systemUser.getLoginTime())) {
            systemUser.setLoginTime(current);
        }
        return systemUser;
    }

    @Override
    public String parseTokenId(String token) throws JwtException {
        return this.parseToken(token).getId();
    }

    @Override
    public String getPrefixOfTokenKey() {
        return securityProperties.isAllowRepeatLogin() ? TOKEN_KEY_SNOW : TOKEN_KEY_ID;
    }

    @Override
    public String generateTokenKey(String id) {
        // 序列ID
        String serialId = snowflake.nextIdStr();
        // 系统是否允许重复登陆
        boolean allowRepeatLogin = securityProperties.isAllowRepeatLogin();

        return allowRepeatLogin ? SecurityUtil.concatTargetStr(TOKEN_KEY_SNOW, serialId, id) : SecurityUtil.concatTargetStr(TOKEN_KEY_ID, id, serialId);
    }

    @Override
    public void refreshToken(String tokenKey, String path) {
        Optional.ofNullable(redisTemplate.boundValueOps(tokenKey).get())
                .ifPresent((Object data) -> {
                    SystemUser user = (SystemUser) data;
                    this.saveToken2Redis(tokenKey, RedisTokenManager.updateData4SystemUser(user, path));
                });
    }

    @Override
    public SystemUserInfo parseTokenSubject(String token) throws JsonProcessingException {
        // 解析 Token
        Claims claims = this.parseToken(token);
        // 获取 Token 中的用户信息
        return objectMapper.readValue(claims.getSubject(), SystemUserInfo.class);
    }

    /*--------------------------------------------------private methods-----------------------------------------------*/

    @Override
    public String generateAndTakeEffectToken(SystemUser systemUser) throws JsonProcessingException {
        // 保存用户数据并获取 tokenKey
        String tokenKey = this.saveTokenData2Redis(systemUser);
        // 将 tokenKey 写入到对象中
        String value = objectMapper.writeValueAsString(systemUser.getSystemUserInfo(tokenKey));
        // 创建 Token 令牌并返回
        return Jwts.builder().setId(systemUser.getId()).setSubject(value).signWith(HS256, secret).compact();
    }

    /**
     * 将用户相关数据存储到 Redis
     *
     * @param tokenKey   缓存Key
     * @param systemUser 缓存Value
     */
    private void saveToken2Redis(String tokenKey, SystemUser systemUser) {
        redisTemplate.boundValueOps(tokenKey).set(systemUser, TOKEN_KEY_EXPIRE);
    }

    /**
     * 解析给定的 Token 令牌
     *
     * @param token 令牌
     *
     * @return 解析结果
     */
    private Claims parseToken(String token) throws JwtException {
        JwtParser parser = Jwts.parser().setSigningKey(secret);

        try {
            return parser.parseClaimsJws(token).getBody();
        } catch (Exception exception) {
            throw new JwtException();
        }
    }

    /**
     * 持久化Token令牌到Redis中
     *
     * @param user 用户数据
     *
     * @return 保存Token的Key
     */
    private String saveTokenData2Redis(SystemUser user) {
        String tokenKey = this.generateTokenKey(user.getId());
        if (!securityProperties.isAllowRepeatLogin()) {
            // 获取用户匹配的正则表达式
            final String key = SecurityUtil.concatTargetStr(TOKEN_KEY_ID, user.getId(), PATTERN_ANY);
            // 删除 Redis 中存储的 Token 数据
            redisTemplate.delete(redisTemplate.keys(key));
        }
        // 更新 SystemUser 中的数据
        SystemUser systemUser = updateData4SystemUser(user, PATH);
        // 保存用户数据到 Redis 中
        this.saveToken2Redis(tokenKey, systemUser);
        return tokenKey;
    }
}
